
import javax.swing.*;//JFrame  、 JOptionPane引入
import java.awt.*;
import java.awt.event.MouseEvent;//鼠标事件 用到getX，getY
import java.awt.event.MouseListener;//鼠标监听
import java.awt.image.BufferedImage;
//JFrame是一个底层的容器
public  class ChessJFrame extends JFrame implements MouseListener,Runnable {
    public static void main(String args[]){
        new ChessJFrame();
    }
    //继承 JFrame类，实现 MouseListener接口 和 Runnable接口
    // 使用实现接口Runnable的对象来创建线程
    // 获取显示器屏幕大小

    int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    int height = Toolkit.getDefaultToolkit().getScreenSize().height;

    int x, y;  // 定义鼠标的坐标

    int[][] allChess = new int[17][17];   // 用数组来保存棋子，0表示无子，1表示黑子，2表示白子
    boolean isblack= true;   //用来表示黑子还是白子， true表示黑子   false表示白子
    boolean canPlay = true;   // 用来表示当前游戏是否结束
    String message = "黑方先行";

    String blackMessage = "无限制";
    String whiteMessage = "无限制";

    int MaxTime=0;//计时器 - 倒计时 设置的时长
    Thread t= new Thread(this);//创建线程
    int blackT=0;//记录黑方和白方的剩余时间
    int whiteT=0;

    //保存棋谱，记录双方每一步落子的位置
    int[] chessX = new int[255];
    int[] chessY = new int[255];
    int countX, countY;

    //定义标志位 游戏胜负已定 不可执行悔棋
    boolean flag1=true;
    //未落子时不可执行悔棋，标志位
    boolean flag2=false;

    public ChessJFrame() {
        //标题
        this.setTitle("简易版五子棋");
        //窗体大小
        this.setSize(600, 600);
        //窗体弹出位置始终保证在屏幕中央
        this.setLocation((width - 500) / 2, (height - 500) / 2);
        //JFrame执行关闭操作时，将退出程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口不可改变，固定窗口大小
        this.setResizable(false);
        //使窗体可见
        this.setVisible(true);
        // 刷新页面，并再调用paint()方法
        this.repaint();
        //打开鼠标事件（按下、释放、单击、进入或离开）的侦听器接口
        this.addMouseListener(this);
        //开启线程-开始时线程挂起
        t.start();
        t.suspend();
    }

    /*
     对于轻量级组件，一般是重写 paint 方法以快速的绘制组件，但是对于重量及组件，由于重新绘制时间长，容易产生闪烁的现象，
     所以一般是采用重写 update 方法，利用双缓冲图片来解决闪烁的问题。
     repaint -> update -> paint
     repaint,update和paint这三个方法在Component中定义,由于awt,swing组件都直接或间接继承自Component,
     所以几乎所有的awt,swing组件都有这三个方法.
     */
    public void paint(Graphics g) {
        //双缓冲流，防止屏闪（将信息都缓存在内存信息中，统一画出）
        BufferedImage buf = new BufferedImage(600, 600, BufferedImage.TYPE_INT_RGB);
        // 创建画笔 在已存在的窗体或控件上绘图
        Graphics g1 = buf.createGraphics();
        //设置填充颜色 画一个着色块
        g1.setColor(new Color(0, 169, 158));
        //画突出矩形
        g1.fill3DRect(40, 80, 400, 400, true);

        /*绘制棋盘16x16*/
        for (int i = 0; i <= 16; i++) {
            //设置画笔颜色
            g1.setColor(Color.WHITE);
            g1.drawLine(40, 80 + i * 25, 400 + 40, 80 + i * 25);  //画棋盘横线
            g1.drawLine(40 + i * 25, 80, 40 + i * 25, 400 + 80);  //画棋盘竖线
        }
        //设计字体显示效果 (String 字体，int 风格，int 字号)
        g1.setFont(new Font("黑体", Font.BOLD, 15));//黑体 加粗 15号
        g1.drawString("游戏信息:" + message, 50, 60);

        //        画黑方时间与白方时间字符串的边框（画矩形）
        g1.drawRect(30, 500, 180, 40);
        g1.drawRect(250, 500, 180, 40);

        g1.setFont(new Font("宋体", 0, 17));
        g1.drawString("黑方时间: " + blackMessage, 40, 520);
        g1.drawString("白方时间: " + whiteMessage, 260, 520);

        //绘制按钮框
        g1.setFont(new Font("宋体", 0, 15));
        g1.drawRect(500, 70, 80, 35);
        g1.drawString("重新开始", 510, 95); //重新开始按钮

        g1.drawRect(500, 150, 80, 35);
        g1.drawString("游戏设置", 510, 175); //游戏设置按钮

        g1.drawRect(500, 230, 80, 35);
        g1.drawString("游戏说明", 510, 255); // 游戏说明按钮

        g1.drawRect(500, 310, 80, 35);
        g1.drawString("退出游戏", 510, 335);  // 退出游戏按钮

        g1.drawRect(500, 380, 80, 35);
        g1.drawString("  悔棋 ", 510, 405);  // 悔棋

        g1.drawRect(500, 460, 80, 35);
        g1.drawString("  认输 ", 510, 485);  // 认输

        g1.drawRect(500, 520, 80, 35);
        g1.drawString("  关于 ", 510, 545);  // 关于


        //画棋子
        for (int i = 0; i <=16; i++) {
            for (int j = 0; j <=16; j++) {
                //画实心黑子
                if (allChess[i][j] == 1) {
                    int tempX = i * 25 + 40;
                    int tempY = j * 25 + 80;
                    g1.setColor(Color.BLACK);
                    // fillOval 绘制实心椭圆 默认黑色填充
                    g1.fillOval(tempX - 10, tempY - 10, 16, 16);
                }
                //画实心白子
                if (allChess[i][j] == 2) {
                    int tempX = i * 25 + 40;
                    int tempY = j * 25 + 80;
                    g1.setColor(Color.WHITE);//设置填充颜色
                    g1.fillOval(tempX - 10, tempY - 10, 16, 16);
                    g1.setColor(Color.BLACK); //颜色恢复
                    g1.drawOval(tempX - 10, tempY - 10, 16, 16);
                }
            }
        }
//        将缓存的内容绘制到窗体上
        g.drawImage(buf, 0, 0, this);
    }


    public void mousePressed(MouseEvent e) {
        //        鼠标按下
        if (canPlay) {
            x = e.getX();
            y = e.getY();  // 用来获取鼠标坐标
            if (x >= 40 && x <= 450 && y >= 80 && y <= 480) {
                //让鼠标在棋盘范围内 计算位置在几行几列
                if((x-40)%25>12){
                    x=(x-40)/25 + 1;
                }else {
                    x = (x-40)/25;
                }
                if((y-80)%25>12){
                    y=(y-80)/25 + 1;
                }else {
                    y=(y-80)/25;
                }
                /*该位置未落子时才可以落子*/
                if(allChess[x][y]==0) {
                    flag2=true;
                    chessX[countX++] = x;
                    chessY[countY++] = y;
                    if (isblack) {
                        allChess[x][y] = 1;
                        isblack = false;
                        message="白方落子";
                    } else {
                        allChess[x][y] = 2;
                        isblack = true;
                        message="黑方落子";
                    }
                    // 刷新页面，并再调用paint()方法
                    this.repaint();
                    boolean win=this.panwin();
                    if(win){
                        t.suspend();//游戏结束，停止计时，线程挂起
                        JOptionPane.showMessageDialog(this,"游戏结束,"+(allChess[x][y]==1?"黑方":"白方")+"获胜");
                        canPlay=false;//不可落子
                        flag1=false;//不可悔棋
                    }
                }
            }
        }
//        重新开始
        if(e.getX()>=499&&e.getX()<=580&&e.getY()>=68&&e.getY()<=110){
            int i = JOptionPane.showConfirmDialog(this, "是否重新开始游戏？");
            if (i==0){//是
                flag2=false;
                flag1=true;//可悔棋
                canPlay=true;//可落子
                //清空棋盘  保存棋子的数组置为0
                allChess=new int[17][17];
                //还原初值
                message="黑方先行";
                //黑方落子
                isblack=true;
                /*时间*/
                blackT=MaxTime;
                whiteT=MaxTime;
                if(MaxTime>0){
                    blackMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
                    whiteMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
                    t.resume();//启动线程，开始倒计时
                } else {
                    blackMessage = "无限制";
                    whiteMessage = "无限制";
                }
                /*调用repaint方法 重新绘图*/
                this.repaint();
            }
        }
//        游戏设置
        if(e.getX()>=499&&e.getX()<=580&&e.getY()>=150&&e.getY()<=180){
            String s = JOptionPane.showInputDialog(this, "请输入游戏时长限制(数值为非负整数，" +
                    "0为无限时间，)：(单位：秒)");
            try {
                MaxTime=Integer.parseInt(s);//将字符串转换为int
                if(MaxTime>=0){
                    int k=-1;
                    if(MaxTime>0)k = JOptionPane.showConfirmDialog(this, "游戏设置完成是否重新开始游戏？");
                    if(MaxTime==0)k = JOptionPane.showConfirmDialog(this, "是否确认将游戏时长设为无限制，并重新开始游戏？");
                    if(k==0){
                        flag2=false;//未落子时不可悔棋
                        flag1=true;//可悔棋
                        canPlay=true;//可落子
                        //清空棋盘  保存棋子的数组置为0
                        allChess=new int[17][17];
                        //还原初值
                        message="黑方先行";
                        //黑方落子
                        isblack=true;
                        /*时间*/
                        if(MaxTime>0){
                            blackT=MaxTime;
                            whiteT=MaxTime;
                            blackMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
                            whiteMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
                            t.resume();//启动线程，开始倒计时
                        }
                        if(MaxTime==0){
                            blackMessage = "无限制";
                            whiteMessage = "无限制";
                            /*调用repaint方法 重新绘图*/
                            t.suspend();//线程挂起
                        }
                        /*调用repaint方法 重新绘图*/
                        this.repaint();
                    }
                }
                else{
                    JOptionPane.showMessageDialog(this,"请输入正确信息，不能是负数");
                }
            }
            catch (NumberFormatException e1) {
                JOptionPane.showMessageDialog(this,"请输入正确信息！");
            }
        }
//        游戏说明
        if(e.getX()>=499&&e.getX()<=580&&e.getY()>=230&&e.getY()<=262){
            JOptionPane.showMessageDialog(this,"黑白棋手轮番执棋子，一" +
                    "方先在横向纵向或斜向上连成五子者获胜！");
        }
//        退出游戏
        if(e.getX()>=499&&e.getX()<=580&&e.getY()>=310&&e.getY()<=339){
            int result = JOptionPane.showConfirmDialog(this, "是否退出游戏？");
            if(result == 0){
                System.exit(0);
            }
        }
//        悔棋
        if(flag1&&flag2) {
            if (e.getX() >= 499 && e.getX() <= 580 && e.getY() >= 380 && e.getY() <= 415) {
                int result = JOptionPane.showConfirmDialog(this,
                        (isblack ? "白方悔棋,黑方是否同意？" : "黑方悔棋，白方是否同意？"));
                if (result == 0) {
                    allChess[chessX[--countX]][chessY[--countY]] = 0;
                    if (isblack) {
                        isblack = false;
                    } else {
                        isblack = true;
                    }
                    if(countX<=0){
                        flag2=false;
                    }
                    this.repaint();  //重绘棋盘
                }
            }
        }
//        认输
        if(e.getX()>=499&&e.getX()<=580&&e.getY()>=462&&e.getY()<=490) {
            if (canPlay) {
                t.suspend();//挂起线程 点击按钮停止计时
                int i = JOptionPane.showConfirmDialog(this, "是否确认认输？");
                if (i == 0) {//是
                    if (isblack) {
                        JOptionPane.showMessageDialog(this, "黑方认输，游戏结束！");
                    } else {
                        JOptionPane.showMessageDialog(this, "白方认输，游戏结束！");
                    }
                    canPlay = false;
                } else {
                    t.resume();//不认输，继续计时
                }
            }
        }
//        关于
        if(e.getX()>=499&&e.getX()<=580&&e.getY()>=520&&e.getY()<=550){
            JOptionPane.showMessageDialog(this,"游戏作者：JAVA二组");
        }
    }
    /*判断输赢*/
    private boolean panwin(){//横向-纵向-斜向 五子连珠
        boolean flag=false;
        int count=0;
        int color=allChess[x][y];
        count=this.checkCount(1,0,color);//横向找
        if(count>=5){
            flag=true;
        }else {
            count=this.checkCount(0,1,color);//纵向找
            if(count>=5){
                flag=true;
            }else {
                count=this.checkCount(1,1,color);//左上--右下
                if(count>=5){
                    flag=true;
                }else {
                    count=this.checkCount(1,-1,color);//左下--右上
                    if(count>=5){
                        flag=true;
                    }
                }
            }
        }
        return flag;
    }
    /**
     * 检查棋盘中的五子棋是否连城五子
     */
    private int checkCount(int xChange , int yChenge ,int color){
        int count = 1;
        int tempX = xChange;
        int tempy = yChenge;  //保存初始值
        while((x+xChange)>=0&&(x+xChange)<=16&&(y+yChenge)<=16&&(y+yChenge)>=0&&color==allChess[x+xChange][y+yChenge]){
            count++;
            if(xChange != 0)  xChange++;
            if(yChenge != 0 ){
                if(yChenge > 0) {
                    yChenge++;
                }else {
                    yChenge--;
                }
            }
        }
        xChange = tempX;
        yChenge = tempy;   // 恢复初始值
        while((x-xChange)>=0&&(x-xChange)<=16&&(y-yChenge)<=16&&(y-yChenge)>=0&&color==allChess[x-xChange][y-yChenge]){
            count++;
            if(xChange != 0)  xChange++;
            if(yChenge != 0 ){
                if(yChenge > 0) {
                    yChenge++;
                }else {
                    yChenge--;
                }
            }
        }
        return count;
    }


    /*重写接口中的方法*/
    public void mouseClicked(MouseEvent e) {
        // TODO Auto-generated method stub  是注释语句，告诉看代码的人，这段代码自动生成。可写可不写
//  鼠标点击判断
    }

    public void mouseReleased(MouseEvent e) {
        // TODO Auto-generated method stub
//  鼠标抬起
    }

    public void mouseEntered(MouseEvent e) {
        // TODO Auto-generated method stub
//   鼠标进入
    }

    public void mouseExited(MouseEvent e) {
        // TODO Auto-generated method stub
// 鼠标离开
    }
    /*
     实现Runnable 接口
        1.定义类实现Runnable 接口
        2. 覆盖Runnable 接口中的run方法
        （将线程要运行的代码存放在该run方法中）
        3. 通过Thread 类建立线程对象
        4.将Runnale接口的子类对象作为实际的参数传递给Thread 类的构造函数
        （为什么要Runnable 接口的自欸对象传递给Thread的子类对象，所以要让线程去指定对象的run方法，就必须明确该run方法所属的对象）
        5.调用Thread 类的start方法开启线程并且调用Runnable 接口的子类的run 方法。*/
    @Override
    public void run() {
        if(MaxTime>0){
            while (true){
                if(isblack){
                    blackT--;
                    if(blackT==0){
                        canPlay=false;
                        blackMessage=0+":"+0+":"+0;//时间用完了
                        this.repaint();
                        JOptionPane.showMessageDialog(this,"黑方超时，游戏结束！");
                        flag1=false;
                        t.suspend();//线程挂起
                    }
                }else {
                    whiteT--;
                    if(whiteT==0){
                        canPlay=false;
                        whiteMessage=0+":"+0+":"+0;//时间用完了
                        this.repaint();
                        JOptionPane.showMessageDialog(this,"白方超时，游戏结束！");
                        flag1=false;
                        t.suspend();//线程挂起
                    }
                }
                blackMessage = blackT/3600+":"+(blackT/60- blackT/3600*60)+":"+(blackT-blackT/60*60);
                whiteMessage = whiteT/3600+":"+(whiteT/60- whiteT/3600*60)+":"+(whiteT-whiteT/60*60);
                this.repaint();
                try {//睡眠1000ms --1s
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
